package cn.texous.demo.dmqs.utils;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpEntity;
import org.apache.http.HttpEntityEnclosingRequest;
import org.apache.http.HttpRequest;
import org.apache.http.NameValuePair;
import org.apache.http.NoHttpResponseException;
import org.apache.http.ParseException;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpRequestRetryHandler;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.config.SocketConfig;
import org.apache.http.conn.ConnectTimeoutException;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.LayeredConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.protocol.HTTP;
import org.apache.http.protocol.HttpContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.PreDestroy;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLHandshakeException;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.io.UnsupportedEncodingException;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

/***/
public class HttpClientUtil {

    private static Logger logger = LoggerFactory.getLogger(HttpClientUtil.class);

    private CloseableHttpClient baseHttpClient;

    private PoolingHttpClientConnectionManager connManager;

    public HttpClientUtil() {
        init();
    }

    // SSL的socket工厂创建
    private static SSLConnectionSocketFactory createSSLConnSocketFactory() {
        SSLConnectionSocketFactory sslsf = null;
        // 创建TrustManager() 用于解决javax.net.ssl.SSLPeerUnverifiedException: peer not
        // authenticated
        X509TrustManager trustManager = new X509TrustManager() {
            @Override
            public X509Certificate[] getAcceptedIssuers() {
                return null;
            }

            @Override
            public void checkClientTrusted(X509Certificate[] arg0,
                                           String authType) throws CertificateException {

            }

            @Override
            public void checkServerTrusted(X509Certificate[] arg0,
                                           String authType) throws CertificateException {

            }
        };
        SSLContext sslContext;
        try {
            sslContext = SSLContext.getInstance(SSLConnectionSocketFactory.TLS);
            sslContext.init(null, new TrustManager[]{trustManager}, null);
            // 创建SSLSocketFactory , // 不校验域名 ,取代以前验证规则
            sslsf = new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return sslsf;
    }

    private void init() {
        ConnectionSocketFactory plainsf = PlainConnectionSocketFactory.getSocketFactory();
        LayeredConnectionSocketFactory sslsf = createSSLConnSocketFactory();
        Registry<ConnectionSocketFactory> registry =
                RegistryBuilder.<ConnectionSocketFactory>create()
                        .register("http", plainsf)
                        .register("https", sslsf)
                        .build();
        connManager = new PoolingHttpClientConnectionManager(registry);
        // 将最大连接数增加到32
        connManager.setMaxTotal(32);
        // 将每个路由基础的连接增加到20
        connManager.setDefaultMaxPerRoute(20);
        // 可用空闲连接过期时间,重用空闲连接时会先检查是否空闲时间超过这个时间，如果超过，释放socket重新建立
        connManager.setValidateAfterInactivity(30000);
        // 设置socket超时时间
        SocketConfig socketConfig = SocketConfig.custom().setSoTimeout(10000).build();
        connManager.setDefaultSocketConfig(socketConfig);
        RequestConfig requestConfig = RequestConfig.custom()
                .setConnectionRequestTimeout(5000)
                .setConnectTimeout(7000)
                .setSocketTimeout(10000)
                .build();
        HttpRequestRetryHandler httpRequestRetryHandler = new HttpRequestRetryHandler() {
            public boolean retryRequest(IOException exception,
                                        int executionCount,
                                        HttpContext context) {
                // 如果已经重试了3次，就放弃
                if (executionCount >= 3) {
                    return false;
                }
                // 如果服务器丢掉了连接，那么就重试
                if (exception instanceof NoHttpResponseException) {
                    return true;
                }
                // 不要重试SSL握手异常
                if (exception instanceof SSLHandshakeException) {
                    return false;
                }
                // 超时
                if (exception instanceof InterruptedIOException) {
                    return true;
                }
                // 目标服务器不可达
                if (exception instanceof UnknownHostException) {
                    return false;
                }
                // 连接被拒绝
                if (exception instanceof ConnectTimeoutException) {
                    return false;
                }
                // ssl握手异常
                if (exception instanceof SSLException) {
                    return false;
                }
                HttpClientContext clientContext = HttpClientContext.adapt(context);
                HttpRequest request = clientContext.getRequest();
                return !(request instanceof HttpEntityEnclosingRequest);
            }
        };
        baseHttpClient = HttpClients.custom()
                .setConnectionManager(connManager)
                .setDefaultRequestConfig(requestConfig)
                .setRetryHandler(httpRequestRetryHandler)
                .build();
        logger.info("init baseHttpClient succ!");
    }

    @PreDestroy
    private void destroy() {
        try {
            baseHttpClient.close();
            logger.info("succ to close httpClient!");
        } catch (IOException e) {
            logger.error("关闭httpClient occur ex", e);
        } finally {
            if (connManager != null) {
                connManager.shutdown();
            }
        }
    }

    /**
     * 发送HTTP_GET请求
     *
     * @return
     */
    public String sendGetRequest(String reqURL) {
        String result = null;
        HttpGet httpget = new HttpGet(reqURL);
        CloseableHttpResponse response = null;
        try {
            CloseableHttpClient httpClient = this.baseHttpClient;
            response = httpClient.execute(httpget);
            int statusCode = response.getStatusLine().getStatusCode();
            if (statusCode == 200) {
                HttpEntity entity = response.getEntity();
                if (null != entity) {
                    result = IOUtils.toString(entity.getContent(), "UTF-8");
                }
            } else {
                logger.info("sendGetRequest {} response code:{}", reqURL, statusCode);
            }
        } catch (ConnectTimeoutException cte) {
            logger.error("请求通信 [{}] 时连接超时", reqURL, cte);
        } catch (SocketTimeoutException ste) {
            logger.error("请求通信 [{}] 时读取超时", reqURL, ste);
        } catch (ClientProtocolException cpe) {
            logger.error("请求通信 [{}] 时协议异常", reqURL, cpe);
        } catch (ParseException pe) {
            logger.error("请求通信 [{}] 时解析异常", reqURL, pe);
        } catch (IOException ioe) {
            logger.error("请求通信 [{}] 时网络异常", reqURL, ioe);
        } catch (Exception e) {
            logger.error("请求通信 [{}] 时偶遇异常", reqURL, e);
        } finally {
            try {
                if (response != null)
                    response.close();
            } catch (Exception e) {
                logger.error("sendGetRequest close response occur ex:", e);
            }
            if (httpget != null) {
                httpget.releaseConnection();
            }
        }
        logger.info("sendGetRequest reqUrl is:{}", reqURL);
        return result;
    }

    public String sendPostRequest(String reqURL, String param) {
        return sendPostRequest(reqURL, param, "");
    }

    /**
     * 发送HTTP_POST请求 type: 默认是表单请求，
     *
     * @return
     */
    public String sendPostRequest(String reqURL, String param, String type) {
        String result = null;
        HttpPost httpPost = new HttpPost(reqURL);
        if (StringUtils.isEmpty(type)) {
            httpPost.setHeader(HTTP.CONTENT_TYPE,
                    "application/x-www-form-urlencoded; charset=utf-8");
        } else {
            httpPost.setHeader(HTTP.CONTENT_TYPE, "application/json; charset=utf-8");
        }
        CloseableHttpResponse response = null;
        try {
            if (param != null) {
                StringEntity entity = new StringEntity(param, "utf-8");
                httpPost.setEntity(entity);
            }
            CloseableHttpClient httpClient = this.baseHttpClient;
            response = httpClient.execute(httpPost);
            int statusCode = response.getStatusLine().getStatusCode();
            if (statusCode == 200) {
                HttpEntity entity = response.getEntity();
                if (null != entity) {
                    result = IOUtils.toString(entity.getContent(), "UTF-8");
                }
            } else {
                logger.info("request {} response code:{}", reqURL, statusCode);
            }
        } catch (ConnectTimeoutException cte) {
            logger.error("请求通信 [{}] 时连接超时", reqURL, cte);
        } catch (SocketTimeoutException ste) {
            logger.error("请求通信 [{}] 时读取超时", reqURL, ste);
        } catch (ClientProtocolException cpe) {
            logger.error("请求通信 [{}] 时协议异常", reqURL, cpe);
        } catch (ParseException pe) {
            logger.error("请求通信 [{}] 时解析异常", reqURL, pe);
        } catch (IOException ioe) {
            logger.error("请求通信 [{}] 时网络异常", reqURL, ioe);
        } catch (Exception e) {
            logger.error("请求通信 [{}] 时偶遇异常", reqURL, e);
        } finally {
            try {
                if (response != null)
                    response.close();
            } catch (IOException e) {
                logger.error("close response occur ex:", e);
            }
            if (httpPost != null) {
                httpPost.releaseConnection();
            }
        }
        logger.info("sendPostRequest reqUrl is:{}", reqURL);
        return result;
    }

    /**
     * 发送get请求不带参数
     *
     * @param url url
     * @return
     */
    private String sendGet(String url) {
        return sendGet(url, null);
    }

    /**
     * 发送get请求
     *
     * @param url url
     * @param params params
     * @return
     */
    private String sendGet(String url, Map<String, String> params) {
        String result = null;
        StringBuilder sb = null;
        // 创建HttpClient对象
        CloseableHttpClient httpClient = this.baseHttpClient;
        // 设置请求参数
        if (null != params && !params.isEmpty()) {
            // 遍历map
            sb = new StringBuilder();
            for (Entry<String, String> entry : params.entrySet()) {
                sb.append("&" + entry.getKey() + "=" + entry.getValue());
            }
            url = url + sb.toString();
        }
        // 创建GET请求方法的实例，并填充url
        HttpGet httpGet = new HttpGet(url);
        try {
            // 发送（执行）请求
            CloseableHttpResponse response = httpClient.execute(httpGet);
            // 获取响应头、内容
            int statusCode = response.getStatusLine().getStatusCode();
            if (statusCode != 200) {
                logger.info("request {} response code:{}", url, statusCode);
            }
            HttpEntity entity = response.getEntity();
            result = IOUtils.toString(entity.getContent(), "UTF-8");
            response.close();
        } catch (Exception e) {
            logger.error("sendGet occur ex:", e);
        }
        return result;
    }

    /**
     * 发送post请求
     *
     * @param url url
     * @param params params
     * @return
     */
    private String sendPost(String url, Map<String, String> params) {
        String result = null;
        // 创建HttpClient对象
        CloseableHttpClient httpClient = HttpClients.createDefault();
        // 创建请求方法实例，填充url
        HttpPost httpPost = new HttpPost(url);
        // 设置请求参数（构造参数队列）
        List<NameValuePair> parmsForm = new ArrayList<NameValuePair>();
        for (Entry<String, String> entry : params.entrySet()) {
            parmsForm.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
        }
        UrlEncodedFormEntity entity;
        try {
            entity = new UrlEncodedFormEntity(parmsForm, "UTF-8");
            // 为方法实例设置参数队列实体
            httpPost.setEntity(entity);
            // 发送（执行）
            CloseableHttpResponse response = httpClient.execute(httpPost);
            // 获取状态码
            int statusCode = response.getStatusLine().getStatusCode();
            if (statusCode != 200) {
                logger.info("request {} response code:{}", url, statusCode);
            }
            // 获取响应内容
            HttpEntity httpEntity = response.getEntity();
            result = IOUtils.toString(httpEntity.getContent(), "UTF-8");
            response.close();
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        } catch (ClientProtocolException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                httpClient.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return result;
    }
}
